<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="dnTLq" id="dnTLq"><span data-lake-id="u42346964" id="u42346964">典型回答</span></h1>
  <p data-lake-id="ua01e4eec" id="ua01e4eec"><span data-lake-id="u5a341596" id="u5a341596">Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。</span></p>
  <p data-lake-id="u2342996d" id="u2342996d"><span data-lake-id="u0cab0846" id="u0cab0846">Stream API可以极大提高Java程序员的生产力，让程序员写出高效率、干净、简洁的代码。</span></p>
  <p data-lake-id="u347063d7" id="u347063d7"><span data-lake-id="u0465ada9" id="u0465ada9">这种风格将要处理的元素集合看作一种流，流在管道中传输，并且可以在管道的节点上进行处理，比如筛选，排序，聚合等。</span></p>
  <p data-lake-id="ub59cdb6a" id="ub59cdb6a"><span data-lake-id="u86280d98" id="u86280d98">Stream有以下特性及优点：</span></p>
  <ul list="ud1fda3fb">
   <li fid="udd5e9e85" data-lake-id="u0564c146" id="u0564c146"><span data-lake-id="ub40039d2" id="ub40039d2">无存储。Stream不是一种数据结构，它只是某种数据源的一个视图，数据源可以是一个数组，Java容器或I/O channel等。</span></li>
   <li fid="udd5e9e85" data-lake-id="ucaa66cdc" id="ucaa66cdc"><span data-lake-id="uea4c5aa3" id="uea4c5aa3">为函数式编程而生。对Stream的任何修改都不会修改背后的数据源，比如对Stream执行过滤操作并不会删除被过滤的元素，而是会产生一个不包含被过滤元素的新Stream。</span></li>
   <li fid="udd5e9e85" data-lake-id="u692f3358" id="u692f3358"><span data-lake-id="u569e76aa" id="u569e76aa">惰式执行。Stream上的操作并不会立即执行，只有等到用户真正需要结果的时候才会执行。</span></li>
   <li fid="udd5e9e85" data-lake-id="u1dee1003" id="u1dee1003"><span data-lake-id="u063affd9" id="u063affd9">可消费性。Stream只能被“消费”一次，一旦遍历过就会失效，就像容器的迭代器那样，想要再次遍历必须重新生成。</span></li>
  </ul>
  <p data-lake-id="uc0fc5846" id="uc0fc5846"><span data-lake-id="ucf25d968" id="ucf25d968">我们举一个例子，来看一下到底Stream可以做什么事情：</span></p>
  <p data-lake-id="udadd066e" id="udadd066e"><img src="https://cdn.nlark.com/yuque/0/2022/jpeg/719664/1668857553666-b12e8359-e999-4db4-b745-192d16150c8c.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_46%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="u9409e00f" id="u9409e00f">​</span></p>
  <p data-lake-id="u9b8146bd" id="u9b8146bd"><span data-lake-id="u0b2330db" id="u0b2330db">上面的例子中，获取一些带颜色塑料球作为数据源，首先过滤掉红色的、把它们融化成随机的三角形。再过滤器并删除小的三角形。最后计算出剩余图形的周长。</span></p>
  <p data-lake-id="ue25e2730" id="ue25e2730"><span data-lake-id="ufd3941e8" id="ufd3941e8">如上图，对于流的处理，主要有三种关键性操作：分别是</span><strong><span data-lake-id="ubf545c8d" id="ubf545c8d">流的创建、中间操作（intermediate operation）以及最终操作(terminal operation)。</span></strong></p>
  <h2 data-lake-id="XYLgV" id="XYLgV"><span data-lake-id="u1184c484" id="u1184c484">Stream的创建</span></h2>
  <p data-lake-id="udd695b2d" id="udd695b2d"><span data-lake-id="u1f02cc12" id="u1f02cc12">在Java 8中，可以有多种方法来创建流。</span></p>
  <p data-lake-id="u75e25dd7" id="u75e25dd7"><span data-lake-id="uab1ec42f" id="uab1ec42f">1、通过已有的集合来创建流</span></p>
  <p data-lake-id="ufaf86391" id="ufaf86391"><span data-lake-id="u1fda3c4a" id="u1fda3c4a">在Java 8中，除了增加了很多Stream相关的类以外，还对集合类自身做了增强，在其中增加了stream方法，可以将一个集合类转换成流。</span></p>
  <p data-lake-id="ubc77e673" id="ubc77e673"><span data-lake-id="ude4cde8b" id="ude4cde8b">List&lt;String&gt; strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis"); Stream&lt;String&gt; stream = strings.stream();</span></p>
  <p data-lake-id="u1d7505a0" id="u1d7505a0"><span data-lake-id="ufbb5735b" id="ufbb5735b">以上，通过一个已有的List创建一个流。除此以外，还有一个parallelStream方法，可以为集合创建一个并行流。</span></p>
  <p data-lake-id="ua7cef491" id="ua7cef491"><span data-lake-id="u181afde1" id="u181afde1">这种通过集合创建出一个Stream的方式也是比较常用的一种方式。</span></p>
  <p data-lake-id="u31009714" id="u31009714"><span data-lake-id="ued659532" id="ued659532">2、通过Stream创建流</span></p>
  <p data-lake-id="u5b050a7c" id="u5b050a7c"><span data-lake-id="u5edf9558" id="u5edf9558">可以使用Stream类提供的方法，直接返回一个由指定元素组成的流。</span></p>
  <p data-lake-id="uc88be5d0" id="uc88be5d0"><span data-lake-id="u5b31642f" id="u5b31642f">Stream&lt;String&gt; stream = Stream.of("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");</span></p>
  <p data-lake-id="uc3263df7" id="uc3263df7"><span data-lake-id="u6a6e2fe6" id="u6a6e2fe6">如以上代码，直接通过of方法，创建并返回一个Stream。</span></p>
  <h2 data-lake-id="rTwvf" id="rTwvf"><span data-lake-id="u6f3b334b" id="u6f3b334b">Stream中间操作</span></h2>
  <p data-lake-id="u906817af" id="u906817af"><span data-lake-id="u922e601c" id="u922e601c">Stream有很多中间操作，多个中间操作可以连接起来形成一个流水线，每一个中间操作就像流水线上的一个工人，每人工人都可以对流进行加工，加工后得到的结果还是一个流。</span></p>
  <p data-lake-id="u80686e9d" id="u80686e9d"><img src="https://cdn.nlark.com/yuque/0/2022/jpeg/719664/1668857553673-54763554-4ff1-4d51-8f12-6d474ba4c67f.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_46%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="u1ae91bd7" id="u1ae91bd7">​</span></p>
  <p data-lake-id="ud55a2890" id="ud55a2890"><span data-lake-id="u2799a1db" id="u2799a1db">以下是常用的中间操作列表:</span></p>
  <p data-lake-id="udefbc83c" id="udefbc83c"><img src="https://cdn.nlark.com/yuque/0/2022/jpeg/719664/1668857553673-956aa8e3-0b42-4965-9351-5e8beb62a03f.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_46%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="u467a2a75" id="u467a2a75">​</span></p>
  <h3 data-lake-id="ucGKB" id="ucGKB"><span data-lake-id="u0b57136b" id="u0b57136b">filter</span></h3>
  <p data-lake-id="u1dfc8aaf" id="u1dfc8aaf"><span data-lake-id="u0e8e2205" id="u0e8e2205">filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤掉空字符串：</span></p>
  <pre lang="java"><code>
List&lt;String&gt; strings = Arrays.asList("Hollis", "", "HollisChuang", "H", "hollis"); 
strings.stream().filter(string -&gt; !string.isEmpty()).forEach(System.out::println); 
//Hollis, HollisChuang, H, hollis
</code></pre>
  <h3 data-lake-id="wgkLj" id="wgkLj"><span data-lake-id="u06100d84" id="u06100d84">map</span></h3>
  <p data-lake-id="ucd8effef" id="ucd8effef"><span data-lake-id="u4c0fa73b" id="u4c0fa73b">map 方法用于映射每个元素到对应的结果，以下代码片段使用 map 输出了元素对应的平方数：</span></p>
  <pre lang="java"><code>
List&lt;Integer&gt; numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); 
numbers.stream().map( i -&gt; i*i).forEach(System.out::println); 
//9,4,4,9,49,9,25
</code></pre>
  <h3 data-lake-id="uuISk" id="uuISk"><span data-lake-id="u8f5304a1" id="u8f5304a1">limit/skip</span></h3>
  <p data-lake-id="ubbc0dbed" id="ubbc0dbed"><span data-lake-id="uee570f81" id="uee570f81">limit 返回 Stream 的前面 n 个元素；skip 则是扔掉前 n 个元素。以下代码片段使用 limit 方法保留4个元素：</span></p>
  <pre lang="java"><code>
List&lt;Integer&gt; numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); 
numbers.stream().limit(4).forEach(System.out::println); //3,2,2,3
</code></pre>
  <h3 data-lake-id="kTgbR" id="kTgbR"><span data-lake-id="u9459d225" id="u9459d225">sorted</span></h3>
  <p data-lake-id="u97cb95cf" id="u97cb95cf"><span data-lake-id="u30873c7b" id="u30873c7b">sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法进行排序：</span></p>
  <pre lang="java"><code>
List&lt;Integer&gt; numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); 
numbers.stream().sorted().forEach(System.out::println); //2,2,3,3,3,5,7
</code></pre>
  <h3 data-lake-id="yK566" id="yK566"><span data-lake-id="u486b2167" id="u486b2167">distinct</span></h3>
  <p data-lake-id="u5628ce0d" id="u5628ce0d"><span data-lake-id="uc6f3c45f" id="uc6f3c45f">distinct主要用来去重，以下代码片段使用 distinct 对元素进行去重：</span></p>
  <pre lang="java"><code>
List&lt;Integer&gt; numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); 
numbers.stream().distinct().forEach(System.out::println); //3,2,7,5
</code></pre>
  <p data-lake-id="u94673875" id="u94673875"><span data-lake-id="u7f5e4dbe" id="u7f5e4dbe">接下来我们通过一个例子和一张图，来演示下，当一个Stream先后通过filter、map、sort、limit以及distinct处理后会发生什么。</span></p>
  <p data-lake-id="u89e3da93" id="u89e3da93"><span data-lake-id="uaf294801" id="uaf294801">代码如下：</span></p>
  <pre lang="java"><code>
List&lt;String&gt; strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis"); 
Stream s = strings.stream().filter(string -&gt; string.length()&lt;= 6)
    .map(String::length).sorted()
    .limit(3)             
    .distinct();
</code></pre>
  <p data-lake-id="u738efb80" id="u738efb80"><span data-lake-id="u33fa6fa7" id="u33fa6fa7">过程及每一步得到的结果如下图：</span></p>
  <p data-lake-id="u33457aa2" id="u33457aa2"><img src="https://cdn.nlark.com/yuque/0/2022/jpeg/719664/1668857553670-9579460d-5027-4ee8-aec5-229a1c8ad968.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_29%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <h2 data-lake-id="yfwPW" id="yfwPW"><span data-lake-id="u23b81d90" id="u23b81d90">Stream最终操作</span></h2>
  <p data-lake-id="ubad6625c" id="ubad6625c"><span data-lake-id="ua6a59f04" id="ua6a59f04">Stream的中间操作得到的结果还是一个Stream，那么如何把一个Stream转换成我们需要的类型呢？比如计算出流中元素的个数、将流装换成集合等。这就需要最终操作（terminal operation）</span></p>
  <p data-lake-id="ubed78ae3" id="ubed78ae3"><span data-lake-id="u5b3fc09f" id="u5b3fc09f">最终操作会消耗流，产生一个最终结果。也就是说，在最终操作之后，不能再次使用流，也不能在使用任何中间操作，否则将抛出异常：</span></p>
  <p data-lake-id="ue7112558" id="ue7112558"><code data-lake-id="u89b9caab" id="u89b9caab"><span data-lake-id="u95d21b21" id="u95d21b21">java.lang.IllegalStateException: stream has already been operated upon or closed</span></code></p>
  <p data-lake-id="u59d63dd1" id="u59d63dd1"><span data-lake-id="u3e496521" id="u3e496521">俗话说，“你永远不会两次踏入同一条河”也正是这个意思。</span></p>
  <p data-lake-id="u1988c106" id="u1988c106"><span data-lake-id="u80b330f3" id="u80b330f3">常用的最终操作如下图：</span></p>
  <p data-lake-id="ue9b0cec2" id="ue9b0cec2"><img src="https://cdn.nlark.com/yuque/0/2022/jpeg/719664/1668857553666-c22a6d72-8f0f-43f5-ada1-e9435df38f75.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_46%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <h3 data-lake-id="qGTf8" id="qGTf8"><span data-lake-id="ub0a25f39" id="ub0a25f39">forEach</span></h3>
  <p data-lake-id="u77eefa08" id="u77eefa08"><span data-lake-id="u439213f8" id="u439213f8">Stream 提供了方法 ‘forEach’ 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数：</span></p>
  <pre lang="java"><code>
Random random = new Random(); 
random.ints().limit(10).forEach(System.out::println);
</code></pre>
  <h3 data-lake-id="PjfAB" id="PjfAB"><span data-lake-id="uea97a454" id="uea97a454">count</span></h3>
  <p data-lake-id="u49eb0959" id="u49eb0959"><span data-lake-id="u6bab647d" id="u6bab647d">count用来统计流中的元素个数。</span></p>
  <pre lang="java"><code>
List&lt;String&gt; strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis"); 
System.out.println(strings.stream().count()); //7
</code></pre>
  <h3 data-lake-id="CPhPf" id="CPhPf"><span data-lake-id="u93f7d0b0" id="u93f7d0b0">collect</span></h3>
  <p data-lake-id="ua9c2e032" id="ua9c2e032"><span data-lake-id="ud4b1a619" id="ud4b1a619">collect就是一个归约操作，可以接受各种做法作为参数，将流中的元素累积成一个汇总结果：</span></p>
  <pre lang="java"><code>
List&lt;String&gt; strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
strings  = strings.stream().filter(string -&gt; string.startsWith("Hollis")).collect(Collectors.toList()); 
System.out.println(strings); //Hollis, HollisChuang, Hollis666, Hollis
</code></pre>
  <p data-lake-id="u46a7ac0e" id="u46a7ac0e"><span data-lake-id="u942870d7" id="u942870d7">接下来，我们还是使用一张图，来演示下，前文的例子中，当一个Stream先后通过filter、map、sort、limit以及distinct处理后会，在分别使用不同的最终操作可以得到怎样的结果：</span></p>
  <p data-lake-id="uda77a8e7" id="uda77a8e7"><span data-lake-id="u23cd62e9" id="u23cd62e9">下图，展示了文中介绍的所有操作的位置、输入、输出以及使用一个案例展示了其结果。</span><img src="https://cdn.nlark.com/yuque/0/2022/jpeg/719664/1668857554694-4279c522-5497-46f1-ab48-8e5270f8fa34.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_24%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"><span data-lake-id="u39a8e927" id="u39a8e927">​</span></p>
 </body>
</html>